Go とか Rust とか Bun とかで生成されるシングルバイナリの差について Gemini に聞いてみた
Geminiに以下を聞いてみました
「Go, Bun, Rust などで single binary へのコンパイルした際にバイナリのサイズが異なるのはなぜでしょうか?」
🦀 Rust: 「必要なものだけを詰め込む」 (サイズ: 小)
Rust は C++ と同様に、ガベージコレクション(GC)を持たず、LLVM という強力なコンパイラ基盤を使って極限まで最適化された機械語を生成します。
ランタイム: ほぼゼロです(最小限のセットアップコードのみ)。
特徴: 使っていない関数やライブラリは徹底的に削除(Dead Code Elimination)されます。
結果: "Hello World" レベルであれば数百KB、実用的なアプリでも数MB程度に収まります。
🐹 Go: 「管理システム(GC)を同梱する」 (サイズ: 中)
Go はネイティブな機械語にコンパイルされますが、実行時にメモリ管理や並行処理を行うための「ランタイム」をバイナリに埋め込みます。
ランタイム: ガベージコレクタ(GC)、ゴルーチン(スケジューラ)、メモリ管理システムが含まれます。
特徴: コードがたった1行でも、裏で動くGCなどのシステム一式が必ず付いてきます。また、標準ライブラリ(net/httpなど)が分厚く、少し使うだけで静的リンクされるためサイズが増えがちです。
結果: "Hello World" でも 2MB〜 程度になります。
🥟 Bun: 「ブラウザ(エンジン)を丸ごと同梱する」 (サイズ: 特大)
Bun (または Node.js の pkg など) のシングルバイナリ化は、厳密には「コンパイル」というより**「実行エンジンとソースコードの梱包」**に近いです。
ランタイム: JavaScriptCore(Safariで使われるJSエンジン)、Bun自体のAPI実装(Zig/C++製)、イベントループなどが全て含まれます。
特徴: あなたの書いたJSコードを実行するために、ブラウザの心臓部をそのままバイナリの中に入れています。
結果: コードが空っぽでも、エンジンのサイズ分(約 90MB〜)が必ず発生します。
上司に聞いてみたところ大体合っているそう
静的リンクに注目すると Go, Rust での差はもう少し小さくなるそう
(Rust は glibc に dylink するからね)